/*  ------------------------------------------------------------------------
	 NQ Common Services Library
	 
	 Homepage: http://www.awzhome.de/
	 ------------------------------------------------------------------------
	 
	 The contents of this file are subject to the Mozilla Public License
	 Version 1.1 (the "License"); you may not use this file except in
	 compliance with the License. You may obtain a copy of the License at
	 http://www.mozilla.org/MPL/
	 
	 Software distributed under the License is distributed on an "AS IS"
	 basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
	 License for the specific language governing rights and limitations
	 under the License.
	
	 The Original Code is code of NQ Common Services Library.

	 The Initial Developer of the Original Code is Andreas Weizel.
	 Portions created by the Initial Developer are
	 Copyright (C) 2006-2011 Andreas Weizel. All Rights Reserved.
	 
	 Contributor(s): (none)
	 
	 Alternatively, the contents of this file may be used under the terms
	 of the GNU General Public License Version 2.0 or later (the "GPL"),
	 or the GNU Lesser General Public License Version 2.1 or later (the
	 "LGPL"), in which case the provisions of the GPL or the LGPL are
	 applicable instead of those above. If you wish to allow use of your
	 version of this file only under the terms of the GPL or the LGPL and
	 not to allow others to use your version of this file under the MPL,
	 indicate your decision by deleting the provisions above and replace
	 them with the notice and other provisions required by the GPL or the
	 LGPL. If you do not delete the provisions above, a recipient may use
	 your version of this file under the terms of any of the MPL, the GPL
	 or the LGPL.
	 ------------------------------------------------------------------------
*/

using System;
using System.Collections.Generic;
using System.Text;
using AWZhome.NQ.Core;
using AWZhome.NQ.Core.Fluent;
using System.Net;
using System.IO;
using System.Threading;
using System.Xml.Serialization;
using System.Security.Cryptography;
using System.Xml;
using System.Linq;
using System.Diagnostics;
using AWZhome.NQ.CommonServices.Fluent;

namespace AWZhome.NQ.CommonServices
{
	[NQExportedService(SingleInstance = true)]
	internal class NQUpdateService : Services.IUpdateService
	{
		private static ManualResetEvent allDone = new ManualResetEvent(false);

		private Dictionary<string, object> _urls = new Dictionary<string, object>();
		private IProduct[] _products = null;
		private IDistribution[] _distributions = null;
		private bool _updateInformationLoaded = false;
		private bool _updatesDownloaded = false;
		private string _updateTempDir = null;
		private UpdateOperationType _currentOperation = UpdateOperationType.NoOperation;
		private JobReleaseList _jobReleaseList = null;

		#region Data classes

		private class RequestState
		{
			public const int BufferSize = 1024;
			public WebRequest WebRequest;
			public WebResponse WebResponse;
			public Stream ResponseStream;
			public byte[] DataBuffer = null;
			public MemoryStream DataStream;

			public RequestState()
			{
				this.DataBuffer = new byte[BufferSize];
				this.DataStream = new MemoryStream();
			}
		}

		private class DownloadProgressState
		{
			public List<DownloadProgressFile> FileList = new List<DownloadProgressFile>();
			public int CurrentIndex;
			public int ProgressStep;
			public int ProgressStepCount;
			public WebClient WebClient;
			public string NQTempDir;
			public long DownloadedBytes = 0;
		}

		private class DownloadProgressFile
		{
			public string FileComponent;
			public string ProductDisplayName;
			public IUpdateFile UpdateFile;
			public bool FileInstalled;
			public string LocalFileName;
		}

		#endregion

		#region Constructor

		public NQUpdateService()
		{
			_jobReleaseList = new JobReleaseList(this);

			// Collect all known update definition URLs
			//string[] components = NQServiceManager.Instance.GetComponentList();
			//for (int i = 0; i < components.Length; i++)
			//{
			//   INQComponentInfo comp = NQServiceManager.Instance.GetComponentInfo(components[i]);
			//   if (!String.IsNullOrEmpty(comp.UpdaterURL) && !_urls.ContainsKey(comp.UpdaterURL))
			//   {
			//      _urls.Add(comp.UpdaterURL, null);
			//   }
			//}
		}

		#endregion

		#region INQUpdateService Members

		public event System.EventHandler<UpdateProgressEventArgs> UpdateProgress;
		public event System.EventHandler<UpdateErrorEventArgs> UpdateError;

		public IProduct[] Products
		{
			get
			{
				lock (this)
				{
					return _products;
				}
			}
		}

		public IDistribution[] Distributions
		{
			get
			{
				lock (this)
				{
					return _distributions;
				}
			}
		}

		public bool UpdateInformationLoaded
		{
			get
			{
				lock (this)
				{
					return _updateInformationLoaded;
				}
			}
		}

		public UpdateOperationType CurrentOperation
		{
			get
			{
				lock (this)
				{
					return _currentOperation;
				}
			}
		}

		public IList<IRelease> JobList
		{
			get
			{
				return _jobReleaseList;
			}
		}

		public void CheckForUpdates()
		{
			// Get update URL list from configuration
			List<string> urls = new List<string>();
			var updateURLSettings = Configuration
				.List("updateURLs")
				.InGroup("NQCS")
				.OfComponents()
				.Get;
			if (updateURLSettings != null)
			{
				foreach (var urlSetting in updateURLSettings)
				{
					urls.Add(urlSetting.AsString());
				}
			}

			this.CheckForUpdates(urls);
		}

		public void CheckForUpdates(IEnumerable<string> updateURLs)
		{
			// Reset the internal URL list
			_urls.Clear();
			foreach (string url in updateURLs)
			{
				_urls.Add(url, null);
			}

			if (_urls.Count == 0)
			{
				// We have no URLs to get updates from, throw exception and get out here
				throw new UpdateException("No update sources defined.", UpdateExceptionType.NoSources);
			}

			// Now do the check
			if (_currentOperation == UpdateOperationType.NoOperation)
			{
				lock (this)
				{
					// Set current operation (for notification purposes)
					_currentOperation = UpdateOperationType.LoadingUpdateInformation;
				}

				// Get references of needed services
				IResourceManager resService = ServiceCore
					.Get<Services.IResourceService>()
					.To;
				IConfigManager configService = ServiceCore
					.Get<Services.IConfigService>()
					.To;

				// Set the HTTP request timeout
				int timeout  = configService.GetComponentSetting("NQCS", "updateHttpTimeout").AsInt32(0) * 1000;
				if (timeout <= 0)
				{
					timeout = 120000;
				}

				// Request the update definitions
				foreach (string url in _urls.Keys)
				{
					try
					{
						Uri uri = new Uri(url);
						NQServiceManager.Instance.InvokeSyncMethod<UpdateProgressEventArgs, bool>(
							this.OnUpdateProgress,
							new UpdateProgressEventArgs(UpdateProgressEventType.Connecting, uri.Host, 0));

						WebRequest webRequest = WebRequest.Create(url);
						RequestState state = new RequestState();
						state.WebRequest = webRequest;

						// Start HTTP GET request
						IAsyncResult result = (IAsyncResult) webRequest.BeginGetResponse(new AsyncCallback(HttpResponseCallback), state);

						// Implement a custom timeout mechanism
						ThreadPool.RegisterWaitForSingleObject(
							result.AsyncWaitHandle,
							new WaitOrTimerCallback(HttpTimeoutCallback), webRequest, timeout, true);
					}
					catch (Exception ex)
					{
						// Notify caller about the error
						NQServiceManager.Instance.InvokeSyncMethod<UpdateErrorEventArgs, bool>(
							this.OnUpdateError, new UpdateErrorEventArgs(ex.Message));
					}
				}
			}
			else
			{
				// Another operation is running already
				throw new UpdateException("The updater is already executing another operation.", UpdateExceptionType.Busy);
			}
		}

		public IInstallCheckResult CheckInstallConditions(IUpdateCondition[] conditions, bool checkJobList)
		{
			NQReleaseTestResult result = new NQReleaseTestResult();
			List<IUpdateCondition> conflictCondList = new List<IUpdateCondition>();
			List<IUpdateCondition> missingCondList = new List<IUpdateCondition>();
			List<string> incompatRelList = new List<string>();

			// Exit if no update information is present
			if (!this.UpdateInformationLoaded)
			{
				throw new UpdateException("No update information is present.", UpdateExceptionType.NotReady);
			}

			// Go through all release's dependencies and check if we can satisfy the release
			// with the currently loaded components or the components chosen in the job list.
			result.IsInstallable = true;
			result.UnresolvedReleaseReferences = false;
			bool condmatched = true;
			if (conditions != null)
			{
				foreach (IUpdateCondition relcond in conditions)
				{
					switch (relcond.ConditionType)
					{
						case UpdateConditionType.Release:
							// Check if required component is loaded
							INQComponentInfo compInfo = NQServiceManager.Instance.GetComponentInfo(relcond.Name);
							if ((compInfo != null) && (relcond.Version != null))
							{
								bool partcond = NQComparer<Version>.CheckCondition(compInfo.Version, relcond.VersionCondition, relcond.Version);
								if (!partcond)
								{
									// The condition doesn't match the current version, check compatibility conditions
									if (compInfo.Compatibility != null)
									{
										foreach (INQComponentRequirement compatItem in compInfo.Compatibility)
										{
											partcond = partcond || NQComparer<Version>.CheckCondition(relcond.Version, compatItem.Condition, compatItem.Version);
										}
									}
								}

								if (!partcond)
								{
									// The component is here, but has not the required version
									conflictCondList.Add(relcond);
									incompatRelList.Add(compInfo.DisplayName + " " + compInfo.DisplayVersion);
									condmatched = false;
								}
							}
							else
							{
								// Add to list of conditions for missing components
								if (relcond.Version != null)
								{
									missingCondList.Add(relcond);
									condmatched = false;
								}
							}

							if (!condmatched && checkJobList)
							{
								// We could not find the required component in current NQ environment, check the job list
								IRelease jobRelease = null;

								for (int i = 0; i < _jobReleaseList.Count; i++)
								{
									if (_jobReleaseList[i].ProductName == relcond.Name)
									{
										jobRelease = _jobReleaseList[i];

										// We have found the component, remove condition from the missing component list
										if ((missingCondList.Count > 0) && (missingCondList[missingCondList.Count - 1] == relcond))
										{
											missingCondList.RemoveAt(missingCondList.Count - 1);
											condmatched = true;
										}

										break;
									}
								}

								// Couldn't find the referenced product
								if (jobRelease != null)
								{
									result.UnresolvedReleaseReferences = true;
								}

								if ((jobRelease != null) && (relcond.Version != null))
								{
									bool partcond = NQComparer<Version>.CheckCondition(jobRelease.Version, relcond.VersionCondition, relcond.Version);
									if (!partcond)
									{
										// The condition doesn't match the current version, check compatibility conditions
										if (jobRelease.Compatibility != null)
										{
											foreach (IUpdateCondition compatItem in jobRelease.Compatibility)
											{
												partcond = partcond || NQComparer<Version>.CheckCondition(relcond.Version, compatItem.VersionCondition, compatItem.Version);
											}
										}
									}

									if (partcond)
									{
										// We have found the required version of component, remove the condition from list
										if ((conflictCondList.Count > 0) && (conflictCondList[conflictCondList.Count - 1] == relcond))
										{
											conflictCondList.RemoveAt(conflictCondList.Count - 1);
											incompatRelList.RemoveAt(incompatRelList.Count - 1);
											condmatched = true;
										}
									}
								}
							}

							result.IsInstallable = result.IsInstallable && condmatched;

							break;

						case UpdateConditionType.File:
							// Check if a file is present in the right version
							string checkedFile = relcond.Name.Replace('/', '\\');
							if (!Path.IsPathRooted(checkedFile))
							{
								// Convert the relative path to an absolute
								checkedFile = Path.Combine(NQServiceManager.Instance.ComponentPath, checkedFile);
							}

							if (File.Exists(checkedFile))
							{
								// Check file version
								FileVersionInfo fileVerInfo = FileVersionInfo.GetVersionInfo(checkedFile);
								if (fileVerInfo != null)
								{
									Version fileVer = new Version(fileVerInfo.FileMajorPart, fileVerInfo.FileMinorPart, fileVerInfo.FileBuildPart, fileVerInfo.FilePrivatePart);
									if (!NQComparer<Version>.CheckCondition(fileVer, relcond.VersionCondition, relcond.Version))
									{
										// Wrong file version
										result.IsInstallable = false;
										conflictCondList.Add(relcond);
									}
								}
								else
								{
									// File has no version information, treat it the same way as a wrong version
									result.IsInstallable = false;
									conflictCondList.Add(relcond);
								}
							}
							else
							{
								// File does not exist, so condition does not match
								result.IsInstallable = false;
								missingCondList.Add(relcond);
							}

							break;

						case UpdateConditionType.OS:
							// Check if the right OS is present

							if (relcond.Name == "windows")
							{
								if (!NQComparer<Version>.CheckCondition(Environment.OSVersion.Version, relcond.VersionCondition, relcond.Version))
								{
									// OS version does not match
									result.IsInstallable = false;
									conflictCondList.Add(relcond);
								}
							}
							else
							{
								// Another system than Windows is requested
								result.IsInstallable = false;
								missingCondList.Add(relcond);
							}

							break;
					}
				}
			}

			// Converting lists to arrays
			result.ConflictConditions = conflictCondList.ToArray();
			result.MissingConditions = missingCondList.ToArray();
			result.IncompatibleReleases = incompatRelList.ToArray();

			return result;
		}

		public void ExecuteUpdateJob()
		{
			// Are there any releases in job list?
			if (!_updateInformationLoaded)
			{
				throw new UpdateException("There is no update information available.", UpdateExceptionType.NotReady);
			}
			if (_jobReleaseList.Count == 0)
			{
				throw new UpdateException("The are no component releases in job list.", UpdateExceptionType.NotReady);
			}

			// Firstly check if all updates in the job list may be installed
			foreach (IRelease release in _jobReleaseList)
			{
				if (!this.CheckInstallConditions(release.Conditions, true).IsInstallable)
				{
					// The release is not compatible with other components
					throw new UpdateException("The release of product \"" + release.ProductName + "\" is not compatible with other installed components.", UpdateExceptionType.Compatibility);
				}
			}

			DownloadProgressState downloadState = new DownloadProgressState();
			downloadState.NQTempDir = Path.Combine(Path.GetTempPath(), "NQInstall");

			// We have to detect the files needed to be downloaded and/or installed
			int stepcount = 0;
			foreach (IRelease release in _jobReleaseList)
			{
				foreach (IUpdateFile file in release.InstallerFiles)
				{
					//if (this.CheckInstallConditions(file.Conditions, true).IsInstallable)
					//{
					// One step for every file download
					stepcount++;

					// Display name for the product
					IProduct product = (from p in _products
												 where p.Name == release.ProductName
												 select p).SingleOrDefault();
					string currentProduct = product.DisplayName + " " + release.DisplayVersion;

					//if (file.IsBootstrap || file.IsPackage)
					//{
					//   // Bootstrap and package files additionally need an installation/unpackage step
					//   stepcount++;
					//}

					// Fill download state object
					downloadState.FileList.Add(new DownloadProgressFile()
							{
								UpdateFile = file,
								FileInstalled = false,
								FileComponent = release.ProductName,
								ProductDisplayName = currentProduct
							}
						);
					//}
				}
			}

			// Save progress information in download state object
			downloadState.CurrentIndex = -1;
			downloadState.ProgressStep = -1;
			downloadState.ProgressStepCount = stepcount;

			// Initialize the WebClient and begin the download operation
			WebClient webClient = new WebClient();
			webClient.DownloadProgressChanged += webClient_DownloadProgressChanged;
			webClient.DownloadFileCompleted += webClient_DownloadFileCompleted;

			downloadState.WebClient = webClient;

			lock (this)
			{
				_currentOperation = UpdateOperationType.LoadingUpdatedFiles;
				_updatesDownloaded = true;
			}

			ExecuteUpdateJobDownloadStep(downloadState);
		}

		public void StartOfflineInstallation()
		{
			// Are there any updates that have been downloaded?
			lock (this)
			{
				if (!_updatesDownloaded)
				{
					throw new UpdateException("No updates are available to start offline installation.", UpdateExceptionType.General);
				}
			}

			// Check if the updater tool is present
			string updaterExe = Path.Combine(NQServiceManager.Instance.ComponentPath, "NQUpdater.exe");
			if (!File.Exists(updaterExe))
			{
				throw new UpdateException("NQUpdater.exe could not be started.", UpdateExceptionType.General);
			}

			lock (this)
			{
				_updatesDownloaded = false;
			}

			// Now start the updater tool
			Process process = new Process();
			process.StartInfo = new ProcessStartInfo(updaterExe, "\"" + _updateTempDir + "\"");
			process.Start();

			// Exit this application, so updater tool can work properly
			NQServiceManager.Instance.ExitApplication();
		}

		#endregion

		#region Private methods

		void webClient_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
		{
			// File download completed, handle that
			NQServiceManager.Instance.InvokeSyncMethod<DownloadProgressState>(
				ExecuteUpdateJobDownloadStep, (DownloadProgressState) e.UserState
				);
		}

		private void webClient_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
		{
			// File download progress has changed, handle that
			lock (NQServiceManager.Instance.SyncObject)
			{
				((DownloadProgressState) e.UserState).DownloadedBytes = e.BytesReceived;
			}
			NQServiceManager.Instance.InvokeSyncMethod<DownloadProgressState>(
				UpdateJobDownloadProgress, (DownloadProgressState) e.UserState
				);
		}

		private void UpdateJobDownloadProgress(DownloadProgressState downloadState)
		{
			DownloadProgressFile progressFile = downloadState.FileList[downloadState.CurrentIndex];

			float mainProgress = ((float) downloadState.ProgressStep / (float) downloadState.ProgressStepCount * 100);
			float subProgress = (1 / (float) downloadState.ProgressStepCount) * ((float) downloadState.DownloadedBytes / (float) progressFile.UpdateFile.Size) * 100;

			// Raise an update event for the download progress
			this.OnUpdateProgress(
				new UpdateProgressEventArgs(
					UpdateProgressEventType.Downloading, progressFile.ProductDisplayName,
					(int) (mainProgress + subProgress)
					)
				);
		}

		private void ExecuteUpdateJobDownloadStep(DownloadProgressState downloadState)
		{
			// Finalize file
			if (downloadState.CurrentIndex > -1)
			{
				DownloadProgressFile downloadFile = downloadState.FileList[downloadState.CurrentIndex];

				// Check file checksum
				string localChecksum = ComputeFileChecksum(downloadFile.LocalFileName);
				if (File.Exists(downloadFile.LocalFileName) && (localChecksum == downloadFile.UpdateFile.Checksum))
				{
					// File has been downloaded correctly
					downloadFile.FileInstalled = true;
				}
				else
				{
					// The download was not correct, we have to abort the operation to avoid inconsistences
					NQServiceManager.Instance.LogMessage(this, NQLogType.Error,
						"Downloaded file " + downloadFile.UpdateFile.Name + " was corrupt (checksum error).");
					this.OnUpdateError(new UpdateErrorEventArgs("Checksum error in downloaded file."));

					lock (this)
					{
						_currentOperation = UpdateOperationType.NoOperation;
					}
				}
			}

			// Download the next file, if there is one
			downloadState.CurrentIndex++;
			downloadState.ProgressStep++;
			this.OnUpdateProgress(
				new UpdateProgressEventArgs(
					UpdateProgressEventType.Downloading, "",
					(int) ((float) downloadState.ProgressStep / (float) downloadState.ProgressStepCount * 100)
					)
				);

			if (downloadState.CurrentIndex < downloadState.FileList.Count)
			{
				DownloadProgressFile progressFile = downloadState.FileList[downloadState.CurrentIndex];

				try
				{
					this.OnUpdateProgress(
						new UpdateProgressEventArgs(
							UpdateProgressEventType.Downloading, progressFile.ProductDisplayName,
							(int) ((float) downloadState.ProgressStep / (float) downloadState.ProgressStepCount * 100)
							)
						);

					// Firstly check if the same file has been already downloaded (comparing checksum)
					string localChecksum = ComputeFileChecksum(progressFile.LocalFileName);
					if (File.Exists(progressFile.LocalFileName) && (localChecksum == progressFile.UpdateFile.Checksum))
					{
						// Continue with the next file...
						ExecuteUpdateJobDownloadStep(downloadState);
					}
					else
					{
						// Start a new download operation
						downloadState.DownloadedBytes = 0;
						downloadState.WebClient.DownloadFileAsync(
							new Uri(progressFile.UpdateFile.SourceURL),
							progressFile.LocalFileName,
							downloadState);
					}
				}
				catch (Exception e)
				{
					// An error occured while downloading the file
					NQServiceManager.Instance.LogMessage(this, NQLogType.Error,
						"Download of file " + progressFile.UpdateFile.Name + " has failed: " + e.Message);
					this.OnUpdateError(new UpdateErrorEventArgs(e.Message));

					// We have to abort the update installation
					lock (this)
					{
						_currentOperation = UpdateOperationType.NoOperation;
					}
				}
			}
			else
			{
				// All files have been downloaded
				ExecuteUpdateJobAfterDownload(downloadState);
			}
		}

		private void ExecuteUpdateJobAfterDownload(DownloadProgressState downloadState)
		{
			// Prepare the installation for the updater
			if (Directory.Exists(downloadState.NQTempDir))
			{
				// Check if all files have been downloaded correctly
				bool downloadCorrect = true;
				foreach (DownloadProgressFile progressFile in downloadState.FileList)
				{
					downloadCorrect &= progressFile.FileInstalled;
				}

				if (downloadCorrect)
				{
					// Create an index file describing all files to install
					try
					{
						using (StreamWriter writer =
							new StreamWriter(Path.Combine(downloadState.NQTempDir, "InstallIndex.dat"), false, Encoding.UTF8))
						{
							// Iterate through downloaded files
							foreach (DownloadProgressFile downloadFile in downloadState.FileList)
							{
								string line = "";
								switch (downloadFile.UpdateFile.ExecutionType)
								{
									case InstallerExecutionType.SelfInstall:
										line = "SELFINST:";
										break;
									case InstallerExecutionType.ZIP:
										line = "ZIP:";
										break;
								}

								line += downloadFile.UpdateFile.Name;
								writer.WriteLine(line);
							}
						}
					}
					catch (IOException ex)
					{
						// An error occured while downloading the file
						NQServiceManager.Instance.LogMessage(this, NQLogType.Error,
							"Could not create index file: " + ex.Message);
						this.OnUpdateError(new UpdateErrorEventArgs(ex.Message));

						// We have to abort the update installation
						lock (this)
						{
							_currentOperation = UpdateOperationType.NoOperation;
						}
					}
				}
			}

			// Now we are ready, clear the job list and notify other components
			_jobReleaseList.Clear();
			this.OnUpdateProgress(new UpdateProgressEventArgs(UpdateProgressEventType.Ready, "", 100));
			lock (this)
			{
				_currentOperation = UpdateOperationType.NoOperation;
				_updateTempDir = downloadState.NQTempDir;
			}
		}

		private void HttpReadCallback(IAsyncResult asyncResult)
		{
			try
			{
				RequestState state = (RequestState) asyncResult.AsyncState;
				Stream responseStream = state.ResponseStream;
				int read = responseStream.EndRead(asyncResult);

				// Read the buffer
				if (read > 0)
				{
					//state.Data.Append(Encoding.GetEncoding("ISO-8859-15").GetString(state.BufferRead, 0, read));
					state.DataStream.Write(state.DataBuffer, 0, read);
					responseStream.BeginRead(state.DataBuffer, 0, RequestState.BufferSize, new AsyncCallback(HttpReadCallback), state);
					return;
				}
				else
				{
					state.DataStream.Flush();
					state.DataStream.Seek(0, SeekOrigin.Begin);
					responseStream.Close();
					state.WebResponse.Close();

					// Now we can interpret the MemoryStream contents as an update definition file
					XmlSerializer serializer = new XmlSerializer(typeof(XmlStub.Updater.RootType));
					XmlStub.Updater.RootType updateRoot = (XmlStub.Updater.RootType) serializer.Deserialize(state.DataStream);

					// We are ready
					lock (this)
					{
						FillInternalDOM(updateRoot);
						_currentOperation = UpdateOperationType.NoOperation;
						_updateInformationLoaded = true;
					}
					NQServiceManager.Instance.InvokeSyncMethod<UpdateProgressEventArgs, bool>(
								this.OnUpdateProgress, new UpdateProgressEventArgs(UpdateProgressEventType.Ready, "", 100));
				}
			}
			catch (WebException we)
			{
				// Notify caller about the error
				NQServiceManager.Instance.InvokeSyncMethod<UpdateErrorEventArgs, bool>(
					this.OnUpdateError, new UpdateErrorEventArgs(we.Message));

				// We are ready
				lock (this)
				{
					_currentOperation = UpdateOperationType.NoOperation;
				}
				//NQServiceManager.Instance.InvokeSyncMethod<NQUpdateProgressEventArgs, bool>(
				//      this.OnUpdateProgress, new NQUpdateProgressEventArgs(NQUpdateProgressEventType.Aborted, "", 100));
			}
		}

		private void HttpResponseCallback(IAsyncResult asyncResult)
		{
			try
			{
				RequestState state = (RequestState) asyncResult.AsyncState;
				WebRequest webRequest = state.WebRequest;
				state.WebResponse = (WebResponse) webRequest.EndGetResponse(asyncResult);

				// Read the response into a Stream object
				state.ResponseStream = state.WebResponse.GetResponseStream();

				// Begin reading the contents
				state.ResponseStream.BeginRead(state.DataBuffer, 0, RequestState.BufferSize, new AsyncCallback(HttpReadCallback), state);
				return;
			}
			catch (WebException we)
			{
				// Notify caller about the error
				NQServiceManager.Instance.InvokeSyncMethod<UpdateErrorEventArgs, bool>(
					this.OnUpdateError, new UpdateErrorEventArgs(we.Message));

				// We are ready
				lock (this)
				{
					_currentOperation = UpdateOperationType.NoOperation;
				}
				//NQServiceManager.Instance.InvokeSyncMethod<NQUpdateProgressEventArgs, bool>(
				//      this.OnUpdateProgress, new NQUpdateProgressEventArgs(NQUpdateProgressEventType.Aborted, "", 100));
			}
		}

		private void HttpTimeoutCallback(object state, bool timedOut)
		{
			if (timedOut)
			{
				// Operation took too long...
				HttpWebRequest request = state as HttpWebRequest;
				if (request != null)
				{
					request.Abort();
				}
			}
		}

		private bool OnUpdateProgress(UpdateProgressEventArgs e)
		{
			if (this.UpdateProgress != null)
			{
				this.UpdateProgress(this, e);
			}

			return true;
		}

		private bool OnUpdateError(UpdateErrorEventArgs e)
		{
			if (this.UpdateError != null)
			{
				this.UpdateError(this, e);
			}

			return true;
		}

		private void FillInternalDOM(XmlStub.Updater.RootType root)
		{
			// Transport all data into internal data model
			List<IProduct> productList = new List<IProduct>();
			if (root.productgroup != null)
			{
				foreach (XmlStub.Updater.ProductGroupType group in root.productgroup)
				{
					if (group.product != null)
					{
						foreach (XmlStub.Updater.ProductType product in group.product)
						{
							// Create new product definition
							NQProduct newProduct = new NQProduct();
							newProduct.ProductGroup = GetLocaleString(group.title);
							newProduct.Name = product.name;
							newProduct.DisplayName = GetLocaleString(product.displayname);
							newProduct.Description = GetLocaleString(product.description);
							newProduct.Text = GetLocaleString(product.text);
							newProduct.URL = product.url;

							List<IRelease> releaseList = new List<IRelease>();
							if (product.releases != null)
							{
								// Iterate through all releases
								foreach (XmlStub.Updater.ReleaseType release in product.releases)
								{
									// Create new release definition
									NQRelease newRelease = new NQRelease();
									newRelease.ProductName = product.name;
									newRelease.Version = new Version(release.ver);
									newRelease.Date = release.date;
									newRelease.IsExperimental = (release.experimentalSpecified && release.experimental);
									newRelease.IsRecommended = (release.recommendedSpecified && release.recommended);
									newRelease.DisplayVersion = GetLocaleString(release.displayversion);
									newRelease.Description = GetLocaleString(release.description);
									newRelease.Conditions = GetConditionObjects(release.conditions);
									newRelease.Compatibility = GetCompatibilityObjects(release.compatibility);
									newRelease.InstallerFiles = GetFileObjects(release.installer);

									// Add the release
									releaseList.Add(newRelease);
								}
							}

							newProduct.Releases = new IRelease[releaseList.Count];
							releaseList.CopyTo(newProduct.Releases);

							// Add the product
							productList.Add(newProduct);
						}
					}

					_products = productList.ToArray();
				}
			}

			// Process distributions
			List<IDistribution> distribList = new List<IDistribution>();
			if (root.distributions != null)
			{
				foreach (XmlStub.Updater.DistributionType distrib in root.distributions)
				{
					NQDistribution newDistrib = new NQDistribution();
					newDistrib.Name = distrib.name;
					newDistrib.DisplayName = GetLocaleString(distrib.displayname);
					newDistrib.DisplayVersion = GetLocaleString(distrib.displayversion);
					newDistrib.Version = new Version(distrib.ver);

					// Now we have to search for all releases and save references to them
					List<IRelease> releaseList = new List<IRelease>();
					if (distrib.releaserefs != null)
					{
						for (int i = 0; i < _products.Length; i++)
						{
							for (int j = 0; j < _products[i].Releases.Length; j++)
							{
								foreach (XmlStub.Updater.ReleaseRefType releaseRef in distrib.releaserefs)
								{
									if ((_products[i].Name == releaseRef.name) && (_products[i].Releases[j].Version.ToString() == releaseRef.ver))
									{
										releaseList.Add(_products[i].Releases[j]);
									}
								}
							}
						}
					}
					newDistrib.Releases = new IRelease[releaseList.Count];
					releaseList.CopyTo(newDistrib.Releases);

					// Add the distribution
					distribList.Add(newDistrib);
				}
			}

			_distributions = distribList.ToArray();

			// Clear job list, since it could now be inconsistent
			_jobReleaseList.Clear();
		}

		private string GetLocaleString(XmlStub.Updater.LocaleTextTypeLocale[] locales)
		{
			string defaultText = String.Empty;

			// Iterate through all localized texts and find the one that fits the current culture
			if (locales != null)
			{
				for (int i = 0; i < locales.Length; i++)
				{
					if (Thread.CurrentThread.CurrentCulture.Name.StartsWith(locales[i].lang))
					{
						// Right localized text was found
						return locales[i].Text[0];
					}
					else if (locales[i].lang.StartsWith("en"))
					{
						// We have an english localization, save it for later usage, when no other localization can be found
						defaultText = locales[i].Text[0];
					}
				}
			}

			return defaultText;
		}

		private IUpdateCondition[] GetConditionObjects(XmlStub.Updater.ConditionType[] conditions)
		{
			if (conditions == null)
			{
				return null;
			}

			List<IUpdateCondition> conditionList = new List<IUpdateCondition>();

			// Iterate through all conditions and create DOM objects
			for (int i = 0; i < conditions.Length; i++)
			{
				NQUpdateCondition cond = new NQUpdateCondition();
				cond.Name = conditions[i].name;
				switch (conditions[i].type)
				{
					case AWZhome.NQ.CommonServices.XmlStub.Updater.ConditionTypeEnum.file:
						cond.ConditionType = UpdateConditionType.File;
						break;

					case AWZhome.NQ.CommonServices.XmlStub.Updater.ConditionTypeEnum.os:
						cond.ConditionType = UpdateConditionType.OS;
						break;

					case AWZhome.NQ.CommonServices.XmlStub.Updater.ConditionTypeEnum.release:
						cond.ConditionType = UpdateConditionType.Release;
						break;

					default:
						cond.ConditionType = UpdateConditionType.Release;
						break;
				}
				Version outVersion;
				NQCondition outCondition;
				ParseVersionCondition(conditions[i].ver, out outVersion, out outCondition);
				cond.Version = outVersion;
				cond.VersionCondition = outCondition;

				// Add to list
				conditionList.Add(cond);
			}

			// Convert List to array and return it
			return conditionList.ToArray();
		}

		private IUpdateCondition[] GetCompatibilityObjects(XmlStub.Updater.CompatibilityType[] compatibilities)
		{
			if (compatibilities == null)
			{
				return null;
			}

			List<IUpdateCondition> compatList = new List<IUpdateCondition>();

			// Iterate through all compatibility conditions and create DOM objects
			for (int i = 0; i < compatibilities.Length; i++)
			{
				NQUpdateCondition cond = new NQUpdateCondition();
				Version outVersion;
				NQCondition outCondition;
				ParseVersionCondition(compatibilities[i].ver, out outVersion, out outCondition);
				cond.Version = outVersion;
				cond.VersionCondition = outCondition;

				// Add to list
				compatList.Add(cond);
			}

			// Convert List to array and return it
			return compatList.ToArray();
		}

		private void ParseVersionCondition(string rawVerCondition, out Version version, out NQCondition condition)
		{
			version = new Version(rawVerCondition.Substring(2));
			switch (rawVerCondition.Substring(0, 2))
			{
				case "++":
					condition = NQCondition.Greater;
					break;
				case "+=":
					condition = NQCondition.Greater | NQCondition.Equal;
					break;
				case "--":
					condition = NQCondition.Lower;
					break;
				case "-=":
					condition = NQCondition.Lower | NQCondition.Equal;
					break;
				case "==":
					condition = NQCondition.Equal;
					break;
				default:
					condition = NQCondition.Equal;
					break;
			}
		}

		private IUpdateFile[] GetFileObjects(XmlStub.Updater.InstallerFileType[] files)
		{
			if (files == null)
			{
				return null;
			}

			List<IUpdateFile> fileList = new List<IUpdateFile>();

			// Iterate through all file definitions
			for (int i = 0; i < files.Length; i++)
			{
				XmlStub.Updater.InstallerFileType fileItem = files[i];
				NQUpdateFile newFile = new NQUpdateFile();

				if (fileItem != null)
				{
					// Process file item
					newFile.Name = fileItem.name;
					newFile.SourceURL = fileItem.source;
					newFile.Size = fileItem.size;
					newFile.Checksum = fileItem.checksum;
					newFile.CommandLine = fileItem.commandline;
					newFile.ExecutionType = this.GetExecutionTypeFromFile(fileItem.type);
				}

				// Add the file definition to the list
				fileList.Add(newFile);
			}

			return fileList.ToArray();
		}

		private InstallerExecutionType GetExecutionTypeFromFile(XmlStub.Updater.InstallerExecutionType executionType)
		{
			switch (executionType)
			{
				case AWZhome.NQ.CommonServices.XmlStub.Updater.InstallerExecutionType.zip:
					return InstallerExecutionType.ZIP;
				case AWZhome.NQ.CommonServices.XmlStub.Updater.InstallerExecutionType.selfinstall:
					return InstallerExecutionType.SelfInstall;
				default:
					return InstallerExecutionType.SelfInstall;
			}
		}

		private string ComputeFileChecksum(string fileName)
		{
			MD5 md5 = MD5.Create();
			StringBuilder sb = new StringBuilder();

			using (FileStream fs = File.Open(fileName, FileMode.Open))
			{
				foreach (byte b in md5.ComputeHash(fs))
				{
					sb.Append(b.ToString("x2").ToLower());
				}
			}

			return sb.ToString();
		}

		#endregion

	}
}
